using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Rotempco.Core.ORM.Objects;
using Rotempco.Core.ORM.Logic;
using System.Threading;
using Rotempco.Core.ORM.Managers;
using Rotempco.Core.ORM.Objects.Stores;
using Rotempco.Core.Logic;
using Rotempco.Core.ORM.Interfaces;
using Rotempco.Core.ORM.Objects.Enums;
using System.Reflection;

namespace Rotempco.Core.ORM.ADAL
{
    public abstract class ADALReadOnly<TDomainObject, TPrimaryKey> : ADALReport<TDomainObject>, IDALReadOnly<TDomainObject, TPrimaryKey>
        where TDomainObject : class, new()
        where TPrimaryKey : struct, IComparable
    {
        protected internal abstract string GetTableName();
        protected internal abstract Pair<string, string> GetIdentity();

        public virtual List<TDomainObject> GetAll(bool getDeleted = false)
        {
            ORMParameter deletedParameter = DeletedParameter();

            if (deletedParameter != null && getDeleted == false)
                return FillObjects("SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "] WHERE [" + deletedParameter.ColumnName + "] IS NULL");
            else
                return FillObjects("SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "]");
        }

        public virtual TDomainObject GetById(TPrimaryKey id, bool getDeleted = false)
        {
            ORMParameter deletedParameter = DeletedParameter();

            string sqlString = "";

            if (deletedParameter != null && getDeleted == false)
                sqlString = "SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "]" + @"
                    WHERE " + GetIdentityColumn() + " = @Id AND [" + deletedParameter.ColumnName + "] IS NULL";
            else
                sqlString = "SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "]" + @"
                    WHERE " + GetIdentityColumn() + " = @Id";

            List<SqlParameter> sqlParameterList = new List<SqlParameter>();
            sqlParameterList.Add(new SqlParameter("Id", id));

            return FillObject(sqlString, sqlParameterList.ToArray());
        }

        public virtual List<TDomainObject> GetByIds(List<TPrimaryKey> idList, bool getDeleted = false)
        {
            if (idList.Count <= 0)
                return new List<TDomainObject>();

            ORMParameter deletedParameter = DeletedParameter();

            string sqlString = "";

            if (deletedParameter != null && getDeleted == false)
                sqlString = "SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "]" + @"
                    WHERE [" + deletedParameter.ColumnName + "] IS NULL AND " + GetIdentityColumn() + " in (" + string.Join(",", idList.ToArray()) + ")";
            else
                sqlString = "SELECT " + GetAllTableColumns() + " FROM [" + GetTableName() + "]" + @"
                    WHERE " + GetIdentityColumn() + " in (" + string.Join(",", idList.ToArray()) + ")";

            return FillObjects(sqlString);
        }

        public virtual List<TDomainObject> GetBy(List<PropertyPair> propertyPairList, bool getDeleted = false, long? getFirstEntries = null, long? getLastEntries = null, List<NullablePair> nullablePairList = null)
        {
            string topString = string.Empty;
            if (getFirstEntries.HasValue && getLastEntries.HasValue)
                throw new Exception("First and Last cannot be set at the same time");

            if (getFirstEntries.HasValue || getLastEntries.HasValue)
                topString = "TOP " + (getFirstEntries.HasValue ? getFirstEntries.Value : getLastEntries.Value) + " ";

            string sqlString = "SELECT " + topString + GetAllTableColumns() + " FROM [" + GetTableName() + "] ";

            string deletedParameter = GetDeletedPropertyName();

            if ((propertyPairList.Count > 0 && (from p in propertyPairList where p.PropertyValue != null select p.PropertyValue).Count() > 0) || (!getDeleted && deletedParameter != null) || (nullablePairList != null && nullablePairList.Count > 0))
                sqlString += " WHERE ";

            List<SqlParameter> sqlParameterList = new List<SqlParameter>();
            List<string> whereList = new List<string>();

            foreach (PropertyPair propertyPair in propertyPairList)
            {
                if (propertyPair.PropertyValue != null)
                {
                    sqlParameterList.Add(new SqlParameter(propertyPair.PropertyName, propertyPair.PropertyValue));
                    whereList.Add(propertyPair.PropertyName + " = @" + propertyPair.PropertyName);
                }
            }

            if (nullablePairList != null)
            {
                foreach (NullablePair nullablePair in nullablePairList)
                {
                    whereList.Add(nullablePair.PropertyName + " " + (nullablePair.IsNull ? "IS NULL" : "IS NOT NULL"));
                }
            }

            sqlString += string.Join(" AND ", whereList);

            if (!getDeleted)
            {

                if (deletedParameter != null)
                {
                    if (whereList.Count > 0)
                        sqlString += " AND ";

                    sqlString += deletedParameter + " IS NULL ";
                }
            }

            if (getFirstEntries.HasValue || getLastEntries.HasValue)
                sqlString += " ORDER BY " + GetIdentityColumn() + (getLastEntries.HasValue ? " DESC " : "");

            return FillObjects(sqlString, sqlParameterList.ToArray());
        }

        public virtual long GetCount(bool getDeleted = false)
        {
            ORMParameter deletedParameter = DeletedParameter();

            if (deletedParameter != null && getDeleted == false)
                return DB.GetExecuteScalar<long>("SELECT COUNT(*) FROM [" + GetTableName() + "] WHERE [" + deletedParameter.ColumnName + "] IS NULL");
            else
                return DB.GetExecuteScalar<long>("SELECT COUNT(*) FROM [" + GetTableName() + "]");
        }

        public List<TDomainObject> GetDeleted()
        {
            ORMParameter deletedParameter = DeletedParameter();

            if (deletedParameter == null)
                throw new NotImplementedException("GetDeleted is not implemented on domainobject: " + typeof(TDomainObject).Name);

            string sqlString = "SELECT " + GetAllTableColumns() + " FROM " + GetTableName() + " WHERE " + DeletedParameter().PropertyName + " IS NOT NULL";

            return FillObjects(sqlString);
        }


        private string GetDeletedPropertyName()
        {
            ORMParameter ormParameter = (from o in GetORMMapping() where o.DeletedProperty == true select o).SingleOrDefault();

            if (ormParameter == null)
                return null;

            return ormParameter.ColumnName;
        }

        protected internal string GetIdentityColumn()
        {
            return GetIdentity().First;
        }

        //public TDomainObject GetByParameterName(string identifierParameter, object identifierParameterValue)
        //{
        //    List<SqlParameter> sqlParameterList = new List<SqlParameter>();
        //    sqlParameterList.Add(new SqlParameter("@IdentifierParameter", identifierParameterValue));

        //    return FillObject("SELECT " + GetAllTableColumns() + " FROM " + GetTableName() + " WHERE [" + identifierParameter + "] = @IdentifierParameter", sqlParameterList.ToArray());
        //}

        # region [ FillObject ]

        protected internal new TDomainObject FillObject(string sqlString)
        {
            return FillObject(sqlString, null as SqlParameter[]);
        }

        protected internal virtual new TDomainObject FillObject(string sqlString, SqlParameter sqlParameter)
        {
            return FillObject(sqlString, new SqlParameter[] { sqlParameter });
        }

        protected internal virtual new TDomainObject FillObject(string sqlString, List<SqlParameter> sqlParameterList)
        {
            return FillObject(sqlString, sqlParameterList.ToArray());
        }

        protected internal virtual new TDomainObject FillObject(string sqlString, SqlParameter[] sqlParameters)
        {
            SqlConnection sqlConnection = DatabaseManager.Instance.SqlConnection;

            DataRow dataRow = DB.GetDataRow(sqlConnection, sqlString, sqlParameters);

            if (dataRow != null)
                return FillObject(dataRow);

            return null;
        }

        protected internal virtual new TDomainObject FillObject(DataRow dataRow)
        {
            TDomainObject instance = new TDomainObject();

            instance.GetType().GetProperty(GetIdentityProperty()).SetValue(instance, DBNullHelper.ToType(dataRow[GetIdentityColumn()], typeof(TPrimaryKey)), null);
            List<ORMParameter> ormMapping = GetORMMapping();

            // Large data encryption
            string largeDataEncryptionKey = null;
            ORMParameter encrytionKey = (from mapping in ormMapping where mapping.ColumnName == GetTableName() + "EncryptionKey" select mapping).FirstOrDefault();
            if (encrytionKey != null)
                largeDataEncryptionKey = AsymmetricEncryptionLogic.DecryptObject(dataRow[encrytionKey.ColumnName] + "", EncryptionKeyStore.Instance.PrivateDecryptionKey).ToString();
            // Large data encryption

            foreach (ORMParameter ormParameter in ormMapping)
            {
                object value = null;

                if (ormParameter.EncryptionType != EncryptionTypeEnum.ClearText && EncryptionStoreManager.Instance.IsEncryptionDisabled())
                    continue;
                else if (ormParameter.EncryptionType != EncryptionTypeEnum.ClearText || ormParameter.SerializableObject)
                {
                    if (ormParameter.EncryptionType == EncryptionTypeEnum.Symmetric)
                    {
                        if (string.IsNullOrWhiteSpace(EncryptionKeyStore.Instance.SymmetricEncryptionKey))
                            throw new Exception("Encryption key not defined");

                        value = SymmetricEncryptionLogic.DecryptObject(dataRow[ormParameter.ColumnName] + "", EncryptionKeyStore.Instance.SymmetricEncryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.ASymmetric)
                    {
                        if (string.IsNullOrWhiteSpace(EncryptionKeyStore.Instance.PrivateDecryptionKey))
                            throw new Exception("Private encryption key not defined");
                        value = AsymmetricEncryptionLogic.DecryptObject(dataRow[ormParameter.ColumnName] + "", EncryptionKeyStore.Instance.PrivateDecryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.RowBasedSymmetric)
                    {
                        if (string.IsNullOrWhiteSpace(largeDataEncryptionKey))
                            throw new Exception("Encryption key not defined");

                        value = SymmetricEncryptionLogic.DecryptObject(dataRow[ormParameter.ColumnName] + "", largeDataEncryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.Krust)
                    {
                        value = null;
                    }

                    if (ormParameter.SerializableObject && ormParameter.EncryptionType != EncryptionTypeEnum.Krust)
                        //value = SerializerLogic.XmlDeserializeObject(ormParameter.Type, dataRow[ormParameter.ColumnName] + "", ormParameter.SerializableRootName);
                        value = SerializerLogic.XmlDeserializeObject(ormParameter.Type, value + "", ormParameter.SerializableRootName);
                }
                else
                    value = dataRow[ormParameter.ColumnName];

                instance.GetType().GetProperty(ormParameter.PropertyName).SetValue(instance, DBNullHelper.ToType(value, ormParameter.Type), null);
            }

            return instance;
        }

        # endregion

        # region [ FillObjects ]

        protected internal new List<TDomainObject> FillObjects(string sqlString)
        {
            return FillObjects(sqlString, null as SqlParameter[]);
        }

        protected internal virtual new List<TDomainObject> FillObjects(string sqlString, SqlParameter sqlParameter)
        {
            return FillObjects(sqlString, new SqlParameter[] { sqlParameter });
        }

        protected internal virtual new List<TDomainObject> FillObjects(string sqlString, List<SqlParameter> sqlParameterList)
        {
            return FillObjects(sqlString, sqlParameterList.ToArray());
        }

        protected internal virtual new List<TDomainObject> FillObjects(string sqlString, SqlParameter[] sqlParameters)
        {
            while (true)
            {
                string currentDatabase = DatabaseManager.Instance.GetCurrentDatabase();

                DataRow[] dataRows = DB.GetDataRows(DatabaseManager.Instance.SqlConnection, sqlString, sqlParameters);

                List<TDomainObject> result = new List<TDomainObject>();
                if (dataRows.Length > 0)
                {
                    foreach (DataRow dataRow in dataRows)
                    {
                        result.Add(FillObject(dataRow));
                    }
                }

                return result;
            }
        }

        # endregion

        protected override internal string GetTableColumns()
        {
            string selectSqlColumns = "";
            List<ORMParameter> ormMapping = GetORMMapping();

            foreach (ORMParameter ormParameter in ormMapping.Where(o => o.EncryptionType != EncryptionTypeEnum.Krust))
                selectSqlColumns += (selectSqlColumns == "" ? "" : ", ") + "[" + GetTableName() + "].[" + ormParameter.ColumnName + @"]";

            return selectSqlColumns;
        }

        protected internal string GetDefaultSelectStatement()
        {
            return "SELECT " + GetAllTableColumns() + " FROM " + GetTableName() + " ";
        }


        protected internal string GetAllTableColumns()
        {
            return "[" + GetTableName() + "].[" + GetIdentityColumn() + "], " + GetTableColumns();
        }


        internal SqlParameter[] GetSqlParameters(TDomainObject instance)
        {
            List<SqlParameter> sqlParameterList = new List<SqlParameter>();
            List<ORMParameter> ormMapping = GetORMMapping();

            // Large data encryption
            string largeDataEncryptionKey = null;
            ORMParameter encryptionKey = (from mapping in ormMapping where mapping.ColumnName == GetTableName() + "EncryptionKey" select mapping).FirstOrDefault();
            if (encryptionKey != null)
            {
                PropertyInfo encryptionKeyProperty = instance.GetType().GetProperty(encryptionKey.PropertyName);
                largeDataEncryptionKey = encryptionKeyProperty.GetValue(instance, null) + "";

                if (string.IsNullOrWhiteSpace(largeDataEncryptionKey))
                {
                    largeDataEncryptionKey = Guid.NewGuid().ToString();
                    instance.GetType().GetProperty(encryptionKey.PropertyName).SetValue(instance, largeDataEncryptionKey, null);
                }
            }
            // Large data encryption

            foreach (ORMParameter ormParameter in ormMapping)
            {
                object value = null;

                string readPropertyName = ormParameter.PropertyName;

                if (ormParameter.EncryptionType == EncryptionTypeEnum.Krust)
                    readPropertyName = readPropertyName.Substring(0, readPropertyName.IndexOf("Krust"));

                object input = instance.GetType().GetProperty(readPropertyName).GetValue(instance, null);

                if (ormParameter.EncryptionType != EncryptionTypeEnum.ClearText && EncryptionStoreManager.Instance.IsEncryptionDisabled())
                    continue;
                else if (input != null && (ormParameter.EncryptionType != EncryptionTypeEnum.ClearText || ormParameter.SerializableObject))
                {
                    if (ormParameter.SerializableObject)
                    {
                        value = SerializerLogic.XmlSerializeObject(input, ormParameter.SerializableRootName);
                        input = value;
                    }

                    if (ormParameter.EncryptionType == EncryptionTypeEnum.Symmetric)
                    {
                        if (string.IsNullOrWhiteSpace(EncryptionKeyStore.Instance.SymmetricEncryptionKey))
                            throw new Exception("Encryption key not defined");

                        value = SymmetricEncryptionLogic.Encrypt(input, EncryptionKeyStore.Instance.SymmetricEncryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.ASymmetric)
                    {
                        if (string.IsNullOrWhiteSpace(EncryptionKeyStore.Instance.PublicEncryptionKey))
                            throw new Exception("Private Encryption key not defined");
                        value = AsymmetricEncryptionLogic.Encrypt(input, EncryptionKeyStore.Instance.PublicEncryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.RowBasedSymmetric)
                    {
                        if (string.IsNullOrWhiteSpace(largeDataEncryptionKey))
                            throw new Exception("Encryption key not defined");

                        value = SymmetricEncryptionLogic.Encrypt(input, largeDataEncryptionKey);
                    }
                    else if (ormParameter.EncryptionType == EncryptionTypeEnum.Krust)
                    {
                        if (EncryptionKeyStore.Instance.KrustKey == null || EncryptionKeyStore.Instance.KrustKey.KrustDictionary == null)
                            throw new Exception("Krust key not defined");

                        value = KrustEncryptionLogic.Encrypt(input + "", EncryptionKeyStore.Instance.KrustKey.KrustDictionary, useSalt: true);
                    }
                }
                else
                    value = input;

                sqlParameterList.Add(new SqlParameter(ormParameter.ColumnName, DBNullHelper.ToNullable(value)));
            }

            return sqlParameterList.ToArray();
        }

        internal string GetIdentityProperty()
        {
            return GetIdentity().Second;
        }

        internal TPrimaryKey GetId(TDomainObject domainObject)
        {
            return (TPrimaryKey)domainObject.GetType().GetProperty(GetIdentityProperty()).GetValue(domainObject, null);
        }

        internal void SetId(TDomainObject domainObject, TPrimaryKey id)
        {
            domainObject.GetType().GetProperty(GetIdentityProperty()).SetValue(domainObject, id, null);
        }

        protected ORMParameter DeletedParameter()
        {
            ORMParameter deletedParameter = (from o in GetORMMapping() where o.DeletedProperty == true select o).SingleOrDefault();
            return deletedParameter;
        }

        public TPrimaryKey DomainIdValue(TDomainObject domainObject)
        {
            return GetId(domainObject);
        }


    }
}
